{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 函数\n",
    "\n",
    "话题：\n",
    "1. 如何声明一个函数\n",
    "2. Julia中的鸭子类型（Duck-typing）\n",
    "3. 原地修改与非原地修改函数 Mutating vs. non-mutating functions\n",
    "4. 高阶函数"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 如何声明一个函数\n",
    "Julia提供了几种不同的声明函数的方法。第一种方法是用`function` 和 `end` 关键字"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "function sayhi(name)\n",
    "    println(\"Hi $name, it's great to see you!\")\n",
    "end"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "function f(x)\n",
    "    x^2\n",
    "end"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "我们可以这样调用函数："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sayhi(\"C-3PO\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "f(42)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "等价的，我们可以只用一行声明函数，拿上面两个函数为例"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sayhi2(name) = println(\"Hi $name, it's great to see you!\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "f2(x) = x^2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sayhi2(\"R2D2\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "f2(42)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "最后，我们可以声明“匿名”函数"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sayhi3 = name -> println(\"Hi $name, it's great to see you!\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "f3 = x -> x^2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sayhi3(\"Chewbacca\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "f3(42)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Julia中的鸭子类型（Duck-typing）\n",
    "*“假如它嘎嘎像个鸭鸭，那它就是个鸭鸭”* <br><br>\n",
    "不管输入参数是什么类型，只要能用，那么Julia函数就能使 <br><br>\n",
    "\n",
    "例如，函数`sayhi`可以接受这个写作整数的小电视角色的名字……"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sayhi(55595472)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "函数`f`可以接受一个矩阵【译注：方阵】"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "A = rand(3, 3)\n",
    "A"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "f(A)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "函数`f`也可以接受字符串比如\"hi\"，因为`*`在接受字符串时用作字符串拼接。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "f(\"hi\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "然而，`f`函数无法接受一个向量（vector）。不同于`A^2`是已经定义好的矩阵乘方，当`v`是向量时的`v^2`是是个没有定义的代数运算。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "v = rand(3)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "f(v)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "## 原地修改与非原地修改函数 Mutating vs. non-mutating functions\n",
    "\n",
    "约定：函数名以`!`结束的原地修改传入的变量，函数名非以`!`结束的则不会改变传入的变量。\n",
    "\n",
    "例如下面的`sort`和`sort!`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "v = [3, 5, 2]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sort(v)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "v"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`sort(v)`会返回一个新的、将`v`排序后的数组，`v`本身不会改变。 <br><br>\n",
    "\n",
    "而当执行`sort!(v)`后，`v`将会被原地排序修改。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sort!(v)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "v"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 一些高阶函数\n",
    "\n",
    "### map\n",
    "\n",
    "`map`是Julia中的高阶函数。“高阶”指的是它会*接受一个函数*作为它的传入参数。\n",
    "`map`将传入的函数作用于传入的数据结构的每一个元素。例如执行\n",
    "\n",
    "```julia\n",
    "map(f, [1, 2, 3])\n",
    "```\n",
    "将得到一个输出数组，数组的每一个元素为`f`作用在数组`[1, 2, 3]`的对应元素\n",
    "```julia\n",
    "[f(1), f(2), f(3)]\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "map(f, [1, 2, 3])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这里我们把向量`[1, 2, 3]`的每一个元素都平方了，而不是对向量`[1, 2, 3]`本身进行平方。\n",
    "\n",
    "`map`可以接受匿名函数作为参数，像这样"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "x -> x^3"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "通过"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "map(x -> x^3, [1, 2, 3])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这就完成了对数组`[1, 2, 3]`所有元素的三次方计算！"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### broadcast\n",
    "\n",
    "`broadcast`和`map`一样也是个高阶函数。`broadcast`一种广义化的`map`,所以`map`能做的它都能做，并不止如此。`broadcast`的调用方法和`map`一样。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "broadcast(f, [1, 2, 3])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "我们又一次通过函数`f`把向量`[1, 2, 3]`的每一个元素都平方了——这次是用“广播”函数`f`的方式！\n",
    "\n",
    "`broadcast`有个语法糖：调用要广播的函数时在函数名和输入变量之间加一个`.`。比如：\n",
    "\n",
    "```julia\n",
    "broadcast(f, [1, 2, 3])\n",
    "```\n",
    "就等价于\n",
    "```julia\n",
    "f.([1, 2, 3])\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "f.([1, 2, 3])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "再一次强调`map`和`broadcast`方式与如下直接调用函数的区别\n",
    "```julia\n",
    "f([1, 2, 3])\n",
    "```\n",
    "我们可以求向量中每个元素的平方，但是不能求向量的平方！"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "为了再讲清楚这一点，我们再看一下以下调用的区别\n",
    "\n",
    "```julia\n",
    "f(A)\n",
    "```\n",
    "与\n",
    "```julia\n",
    "f.(A)\n",
    "```\n",
    "其中`A`是个矩阵："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "A = [i + 3*j for j in 0:2, i in 1:3]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "f(A)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "和之前一样，对于矩阵`A`来说\n",
    "```\n",
    "f(A) = A^2 = A * A\n",
    "``` \n",
    "\n",
    "另一方面，"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "B = f.(A)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "包含了矩阵`A`中每个元素的平方\n",
    "\n",
    "广播的点语法让比较复杂的元素间运算的表示更自然、更贴近数学表示，比如："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "A .+ 2 .* f.(A) ./ A"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "而不是"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "broadcast(x -> x + 2 * f(x) / x, A)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这两种方法本质上完全一样"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "### 练习\n",
    "\n",
    "#### 6.1 \n",
    "声明一个函数`add_one`用来给它的输入参数加1。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "deletable": false,
    "editable": false,
    "hide_input": true,
    "nbgrader": {
     "checksum": "253b17dc2f3d3a58042fbc36042a0fd5",
     "grade": true,
     "grade_id": "cell-5119b9e9623c1cb7",
     "locked": true,
     "points": 1,
     "schema_version": 1,
     "solution": false
    }
   },
   "outputs": [],
   "source": [
    "@assert add_one(1) == 2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "deletable": false,
    "editable": false,
    "hide_input": true,
    "nbgrader": {
     "checksum": "4e05440e19cd3606df11186d41d562bf",
     "grade": true,
     "grade_id": "cell-50f83d27187a2064",
     "locked": true,
     "points": 1,
     "schema_version": 1,
     "solution": false
    }
   },
   "outputs": [],
   "source": [
    "@assert add_one(11) == 12"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 6.2 \n",
    "使用函数`map`或者`broadcast`给矩阵`A`的每一个元素加1并把它赋值给`A1`。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "#### 6.3 \n",
    "使用广播的点语法给矩阵`A`的每一个元素加1并把它赋值给`A2`。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "deletable": false,
    "editable": false,
    "hide_input": true,
    "nbgrader": {
     "checksum": "3e3d797962df904deed0e7ee7782b69a",
     "grade": true,
     "grade_id": "cell-f3bd5479679a8fe1",
     "locked": true,
     "points": 0,
     "schema_version": 1,
     "solution": false
    }
   },
   "outputs": [],
   "source": [
    "@assert A2 == [3 4 5; 6 7 8;9 10 11]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "请在完成练习后点击顶部的`Validate`。"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Julia 1.0.5",
   "language": "julia",
   "name": "julia-1.0"
  },
  "language_info": {
   "file_extension": ".jl",
   "mimetype": "application/julia",
   "name": "julia",
   "version": "1.0.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
